<script setup lang="ts">
/**
 * @desc NodeWrapper
 * @author MiyueFE <https://github.com/miyuesc>
 * @since 2024/7/12 下午2:25
 */

import type { PropType, Ref } from 'vue'
import { computed, ref } from 'vue'
import { isString, isUndefined } from '@/utils/typed'
import type {
  AppendMenuItem,
  BaseNode,
  BaseNodeType,
  CanRuleValidator,
  ExecutionValidator,
  ExpressionNode,
  FlowDirection,
} from '@/types'
import {
  appendNode,
  createNode,
  moveNode,
  removeNode,
  setDragData,
} from '@/utils/element-utils'
import type { GlobalConfigKey } from '@/configuration/global-config'
import { getGlobalConfig } from '@/configuration/global-config'
import { capitalize } from '@/utils/tools'

defineOptions({ name: 'NodeWrapper' })

const $props = defineProps({
  data: {
    type: Object as PropType<BaseNode>,
    required: true,
  },
  direction: {
    type: String as PropType<FlowDirection>,
    default: 'vertical',
    validator: (v: FlowDirection) => ['vertical', 'horizontal'].includes(v),
  },
})

const doEmit = defineEmits(['update:data'])

function getValidatorFc(key: GlobalConfigKey) {
  const config = getGlobalConfig(key)

  if (isUndefined(config))
    return () => true

  if (typeof config === 'function') {
    return config
  }

  return () => config
}

const computedModelNode = computed<BaseNode>({
  get: () => $props.data,
  set: (node: BaseNode) => {
    doEmit('update:data', node)
  },
})

// 操作校验
const appendable = computed(() => {
  const canAppend = getValidatorFc('canAppend') as CanRuleValidator
  return canAppend(computedModelNode.value)
})
const removable = computed(() => {
  const removable = getValidatorFc('canRemove') as CanRuleValidator
  return removable(computedModelNode.value)
})
const movable = computed(() => {
  const movable = getValidatorFc('canMove') as CanRuleValidator
  return movable(computedModelNode.value)
})

// 校验提示
const completenessValid = computed(() => {
  const completenessValidator = getValidatorFc(
    'completenessValidator',
  ) as ExecutionValidator
  return completenessValidator(computedModelNode.value)
})
// 默认条件
const isDefaultFlow = computed(() => {
  return (
    $props.data?.type === 'expression'
    && ($props.data as ExpressionNode).$parent?.$default === $props.data
  )
})

// 追加节点
function appendNewNode(item: AppendMenuItem<BaseNodeType>) {
  let canAppend = getGlobalConfig('canAppend')
  if (typeof canAppend !== 'function') {
    canAppend = () => canAppend as boolean
  }
  if (canAppend(computedModelNode.value)) {
    const newNode = ref(
      createNode(
        item.type,
        computedModelNode.value?.$parent,
        item.name,
        item.businessData,
      ),
    )
    appendNode(computedModelNode, newNode)
  }
}

// 拖拽与放置
function initDrag(e: DragEvent) {
  e.dataTransfer?.setDragImage(e.target! as HTMLDivElement, -6, -6)
  setDragData(computedModelNode)
}
function setDropNode(node: Ref<BaseNode>) {
  if (node.value.id === computedModelNode.value.id) {
    return
  }
  moveNode(computedModelNode, node)
}

// 删除节点
async function handleDeleteNode() {
  try {
    const preBehavior = getGlobalConfig('preBehaviorOfDelete')
    const canDelete = await preBehavior(computedModelNode.value)
    if (canDelete) {
      removeNode(computedModelNode.value)
    }
  }
  catch (e) {
    console.error(e)
  }
}

function transformNodeName(node: BaseNode): string {
  return `${capitalize(node.type)}Node`
}
</script>

<template>
  <div v-if="$props.data" class="flow-node__wrapper">
    <div
      class="flow-node__container"
      :class="{
        'flow-node__movable': movable,
        'flow-node__uncompleted': !completenessValid.status,
      }"
      :draggable="movable"
      @dragstart.stop="initDrag"
      @mousedown.stop
    >
      <!--   未通过校验标识   -->
      <a-popover v-if="!completenessValid.status && completenessValid.message">
        <div class="node-tag-icon node__uncompleted-tag">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="18"
            height="18"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="lucide lucide-circle-alert"
          >
            <line x1="12" x2="12" y1="4" y2="14" />
            <line x1="12" x2="12" y1="18" y2="20" />
          </svg>
        </div>
        <template #content>
          <div>{{ isString(completenessValid.message) ? completenessValid.message : completenessValid.message.join('；') }}</div>
        </template>
      </a-popover>

      <!--   默认条件标识   -->
      <div v-if="isDefaultFlow" class="node-tag-icon node__default-flow-tag">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="18"
          height="18"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-milestone"
        >
          <path d="M12 13v8" />
          <path d="M12 3v3" />
          <path
            d="M4 6a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h13a2 2 0 0 0 1.152-.365l3.424-2.317a1 1 0 0 0 0-1.635l-3.424-2.318A2 2 0 0 0 17 6z"
          />
        </svg>
      </div>

      <!--   删除按钮   -->
      <div v-if="removable" class="node-tag-icon node__delete-tag" title="删除节点">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="18"
          height="18"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-trash-2"
          @click="handleDeleteNode"
        >
          <path d="M3 6h18" />
          <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
          <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
          <line x1="10" x2="10" y1="11" y2="17" />
          <line x1="14" x2="14" y1="11" y2="17" />
        </svg>
      </div>

      <component
        :is="transformNodeName(computedModelNode)"
        v-model:data="computedModelNode"
        :direction="direction"
        :data-node-id="computedModelNode.id"
      />
    </div>
    <NodeBehavior
      v-if="appendable"
      :data="$props.data"
      @append="appendNewNode"
      @dropped="setDropNode"
    />
  </div>
</template>
